#include "User.h"

void User::init(int argc, char* argv[]) {
    logfile.open("log.txt", ios_base::app | ios_base::out);

    logfile << "----------------------------------------------------------------\n";

    time_t rawtime = time(0);
    srand(time(0));
    logfile << "cs294-13 proj3 started " << asctime(localtime(&rawtime));

    eigenstructures = Eigenstructure::readFile(NMax);
    Surface::useEigenstructures(eigenstructures);
    Vertex::useEigenstructures(eigenstructures);

    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100,100);
	glutInitWindowSize(640,480);
	glutCreateWindow("cs294-13 proj3");

    surface = 0;

    //config file
    char* configFilename = "config.txt";
    if (argc >= 2) configFilename = argv[1];
    useConfigFile(configFilename);

    eyePhi = 0;
    eyeTheta = 1;
    eyeDist = 5;

    polygonMode = GL_FILL;
    drawNormals = false;
    hideBackface = false;
    useMeanCurvature = false;
    glEnable(GL_DEPTH_TEST);
    glCullFace(GL_BACK);

    //glEnable(GL_LINE_SMOOTH);
    //glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    glLineWidth(1.0);

    glEnable(GL_POINT_SMOOTH);
    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
    glPointSize(4.0);

    //glDepthFunc(GL_LESS);

    surface->calculateCurvatures();
}

//macro for readability
#define PROCESS_OP(op_name) \
    if (!strcmp(op, #op_name)) { \
        op_ ## op_name ## (configfile); \
        continue; \
    }

void User::useConfigFile(const char *filename) {
    ifstream configfile(filename);
    char op[256];
    
    while (true) {
        configfile >> op;
        if (!configfile.good()) break;

        PROCESS_OP(s);
        PROCESS_OP(cc);
        PROCESS_OP(ccLimit);
        PROCESS_OP(obj);
        PROCESS_OP(opt);

        logfile << "Unrecognized op '" << op << "'\n";
    }

    configfile.close();

    
}

void User::op_s(ifstream& configfile) {
    char filename[256];
    configfile >> filename;
    delete surface;
    surface = new Surface(filename);
    printf("Read surface from '%s'\n", filename);
    logfile << "Read surface from '" << filename << "'.\n";
}

void User::op_cc(ifstream& configfile) {
    Surface* next = surface->cc();
    delete surface;
    surface = next;
    printf("Performed subdivision step.\n");
    logfile << "Performed subdivision step.\n";
}

void User::op_ccLimit(ifstream& configfile) {
    Surface* next = surface->ccLimit();
    delete surface;
    surface = next;
    printf("Pushed to limit surface.\n");
    logfile << "Pushed to limit surface.\n";
}

void User::op_obj(ifstream& configfile) {
    char filename[256];
    double curvatureScale;
    configfile >> filename >> curvatureScale;
    surface->writeObj(filename, false, curvatureScale);
    printf("Read surface to '%s'\n", filename);
    logfile << "Wrote surface to '" << filename << "'.\n";
}

void User::op_opt(ifstream& configfile) {
    char optType[256];
    int n;
    configfile >> optType >> n;
    surface->opt(optType, n);
    printf("Performed %d steps of '%s' optimization.\n", n, optType);
    logfile << "Performed " << n << " steps of '" << optType << "' optimization.\n";
}

void User::setPerspective() const {
    // Reset the coordinate system before modifying
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
	// Set the viewport to be the entire window
    glViewport(0, 0, w, h);
    gluPerspective(45, float(w) / float(h), 1, 1000);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    double eyex = sin(eyePhi) * sin(eyeTheta) * eyeDist;
    double eyey = cos(eyeTheta) * eyeDist;
    double eyez = cos(eyePhi) * sin(eyeTheta) * eyeDist;
	gluLookAt(eyex, eyey, eyez, 
		      0.0, 0.0, 0.0,
			  0.0, 1.0, 0.0);
}

void User::render() {
    clock_t elapsedClock = clock() - lastFrame;
    float elapsedTime = float(elapsedClock) / CLOCKS_PER_SEC;
    lastFrame = clock();

    if (elapsedTime > 1) elapsedTime = 1;

    if (mouseButton == GLUT_LEFT_BUTTON) {
        eyePhi -= diffMouseX * 0.01;
        eyeTheta -= diffMouseY * 0.01;
        if (eyeTheta < 0.01) eyeTheta = 0.01;
        else if (eyeTheta > pi - 0.01) eyeTheta = pi - 0.01;
    }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    setPerspective();

    surface->surfaceGeometry(useMeanCurvature);
    if (drawNormals) {
        surface->normalGeometry();
    }

	glutSwapBuffers();
}

void User::reshape(int w, int h) {
    // Prevent a divide by zero, when window is too short
	// (you cant make a window of zero width).
	if (h == 0) h = 1;

    this->h = h;
    this->w = w;
}

void User::keyboard(unsigned char key, int x, int y) {
    switch (key) {
        case 'x':
            exit(0);
            break;
        case 'w':
            eyeDist -= 0.25;
            break;
        case 's':
            eyeDist += 0.25;
            break;
        case 'p':
            switch (polygonMode) {
                case GL_FILL:
                    polygonMode = GL_LINE;
                    break;
                case GL_LINE:
                    polygonMode = GL_POINT;
                    break;
                case GL_POINT:
                    polygonMode = GL_FILL;
                    break;
            }
            glPolygonMode(GL_FRONT_AND_BACK, polygonMode);
            break;
        case 'n':
            drawNormals = !drawNormals;
            break;
        case 'b':
            hideBackface = !hideBackface;
            if (hideBackface) {
                glEnable(GL_CULL_FACE);
            } else {
                glDisable(GL_CULL_FACE);
            }
            break;
        case 'k':
            useMeanCurvature = !useMeanCurvature;
            if (useMeanCurvature) printf("Drawing mean curvature.\n");
            else printf("Drawing Gaussian curvature.\n");
            break;
    }
}

void User::mouseState(int button, int state, int x, int y) {
    if (state == GLUT_DOWN) {
        mouseButton = button;
        lastMouseX = x;
        lastMouseY = y;
    } else if (state == GLUT_UP) {
        mouseButton = -1;
    }
}

void User::mouseMotion(int x, int y) {
    if (mouseButton >= 0) {
        diffMouseX = x - lastMouseX;
        diffMouseY = y - lastMouseY;
    }
    lastMouseX = x;
    lastMouseY = y;
}
